package com.hwtx.form.util;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.annotation.*;
import com.hwtx.form.annotation.Action;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.def.*;
import com.hwtx.form.domain.def.Select;
import com.hwtx.form.domain.def.meta.*;
import com.hwtx.form.domain.ds.DataSourceContextHolder;
import com.hwtx.form.domain.handle.FormDataHandle;
import com.hwtx.form.domain.handle.FormDefHandle;
import com.hwtx.form.domain.handle.FormI18nHandle;
import com.hwtx.form.domain.handle.datamodel.DataModel;
import com.hwtx.form.domain.handle.datamodel.DataModelDetail;
import com.hwtx.form.domain.meta.FunPermCache;
import com.hwtx.form.domain.repo.ApiRepo;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.repo.FormConfigRepo;
import com.hwtx.form.domain.repo.FormRepo;
import com.hwtx.form.domain.vo.DataModelDetailQuery;
import com.hwtx.form.domain.vo.FormConfigQuerySearchVo;
import com.hwtx.form.domain.vo.FormConfigQueryVo;
import com.hwtx.form.dto.FormDefDto;
import com.hwtx.form.persistence.entity.ApiDef;
import com.hwtx.form.persistence.entity.FormDefDetail;
import io.geekidea.boot.auth.cache.LoginCache;
import io.geekidea.boot.framework.exception.BusinessException;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.stream.Streams;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.domain.def.meta.Button.buildListExtComponents;
import static com.hwtx.form.domain.def.meta.Component.*;
import static com.hwtx.form.domain.def.meta.Component.Page;
import static com.hwtx.form.domain.def.meta.Crud.buildCrud;
import static com.hwtx.form.util.JacksonUtil.readJson;
import static com.hwtx.form.util.JacksonUtil.traverse;

@Slf4j
public class FormUtil {
    public static final String PAGE_ROOT = "page";
    public static final String SYSTEM_PAGE_ROOT = "page/sys";
    public static final String TPL_PAGE_ROOT = "tpl";
    public static final String APP_PAGE_ROOT = "page/app";
    public static final String TMP_PAGE_ROOT = "page/tmp";

    public static String firstCharToUpperCase(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }

    public static String getUrlContext(ServerProperties serverProperties) {
        return serverProperties.getServlet().getContextPath();
    }

    public static Map<String, FormDataHandle> getFromHandles() {
        final Map<String, FormDataHandle> formHandles = Maps.newHashMap();
        Map<String, FormDataHandle> handles = SpringUtil.getApplicationContext().getBeansOfType(FormDataHandle.class);
        handles.values().forEach(handle -> formHandles.put(getFromHandleType(handle).value(), handle));
        return formHandles;
    }

    public static Class<?> getFromHandleClassByClassName(String className) {
        Map<String, FormDataHandle> handles = SpringUtil.getApplicationContext().getBeansOfType(FormDataHandle.class);
        return handles.values().stream().filter(handle -> {
            Object targetClass = AopProxyUtils.getSingletonTarget(handle);
            if (targetClass != null) {
                return Objects.equals(targetClass.getClass().getName(), className);
            }
            return Objects.equals(handle.getClass().getName(), className);
        }).map(FormDataHandle::getClass).findAny().orElse(null);
    }

    public static FormDetailExt.FormExt getFormDatailExtOfFormExt(String ext) {
        FormDetailExt formDetailExt = getFormDetailExt(ext);
        if (formDetailExt == null) {
            return null;
        }
        return formDetailExt.getFormExt();
    }

    public static FormDetailExt getFormDetailExt(String ext) {
        if (StringUtils.isBlank(ext)) {
            return null;
        }
        return JacksonUtil.json2Object(ext, FormDetailExt.class);
    }

    public static List<FormDetailExt.Operation> getFormDetailOperationExt(String ext) {
        FormDetailExt formDetailExt = getFormDetailExt(ext);
        if (formDetailExt == null || formDetailExt.getListExt() == null) {
            return null;
        }
        return formDetailExt.getListExt().getOperations();
    }

    public static FormConfigExt getFormConfigExt(String ext) {
        if (StringUtils.isBlank(ext)) {
            return null;
        }
        return JacksonUtil.json2Object(ext, FormConfigExt.class);
    }

    public static FormConfigExt.FormConfig getFormConfig(String ext) {
        if (StringUtils.isBlank(ext)) {
            return null;
        }
        FormConfigExt formConfigExt = JacksonUtil.json2Object(ext, FormConfigExt.class);
        if (formConfigExt == null) {
            return null;
        }
        return formConfigExt.getForm();
    }

    public static FormConfigExt.ListConfig getListConfig(String ext) {
        if (StringUtils.isBlank(ext)) {
            return null;
        }
        FormConfigExt formConfigExt = JacksonUtil.json2Object(ext, FormConfigExt.class);
        if (formConfigExt == null) {
            return null;
        }
        return formConfigExt.getList();
    }

    public static FormType getFromHandleType(FormDataHandle handle) {
        FormType formType = AnnotationUtils.findAnnotation(handle.getClass(), FormType.class);
        if (formType == null) {
            throw new RuntimeException("FormDataHandle接口实现需要标注@dFormType, FormDataHandle = " + handle.getClass().getName());
        }
        return formType;
    }

    public static Map<Long, FormDefHandle> getFormDefHandles() {
        final Map<Long, FormDefHandle> formDefHandles = Maps.newHashMap();
        Map<String, FormDefHandle> defHandles = SpringUtil.getApplicationContext().getBeansOfType(FormDefHandle.class);
        defHandles.values().forEach(handle -> {
            FormId formId = handle.getClass().getAnnotation(FormId.class);
            if (formId == null) {
                throw new RuntimeException("FormDefHandle接口实现需要标注@FormId, FormDefHandle = " + handle.getClass().getName());
            }
            for (long id : formId.value()) {
                formDefHandles.put(id, handle);
            }
        });
        return formDefHandles;
    }

    public static Long getFormId(File file) {
        String fileName = file.getName();
        String fileNameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.'));
        String[] splits = fileNameWithoutExtension.split("_");
        if (splits.length == 2) {
            return Long.valueOf(splits[1]);
        }
        return null;
    }

    public static FormProperties getLocalFormInfo(File file) throws IOException {
        String fileName = file.getName();
        String fileNameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.'));
        String[] splits = fileNameWithoutExtension.split("_");
        FunPermCache funPermCache = SpringUtil.getBean(FunPermCache.class);
        if (splits.length == 2) {
            FormProperties formProperties = new FormProperties();
            formProperties.setFormId(Long.valueOf(splits[1]));
            String[] names = splits[0].split("-");
            formProperties.setName(splits[0]);
            formProperties.setType(names[0]);
            formProperties.setSystem(Objects.equals(file.getParentFile().getName(), "sys"));
            if (formProperties.getName().endsWith("-form")) {
                formProperties.setClassify(FORM_CLASSIFY_FORM);
            } else {
                formProperties.setClassify(FORM_CLASSIFY_LIST);
            }
            final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            formProperties.setUpdateTime(sdf.format(file.lastModified()));
            if (file.exists()) {
                String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
                if (StringUtils.isNotBlank(content)) {
                    JsonNode root = readJson(content);
                    formProperties.setMeta(getMeta(root, formProperties.getName()));
                    List<String> noAuthUrls = initNoneAuthUrl(root);
                    if (!noAuthUrls.isEmpty()) {
                        funPermCache.putFormNoAuth(formProperties.getFormId(), noAuthUrls);
                    }
                }
            }
            FormConfigRepo formConfigRepo = SpringUtil.getApplicationContext().getBean(FormConfigRepo.class);
            FormDefDetail formDetail = formConfigRepo.getFormDetail(formProperties.getFormId());
            formProperties.setConfig(formDetail != null);
            return formProperties;
        } else {
            throw new BusinessException("表单文件名格式错误,文件名 = " + fileName);
        }
    }

    public static FormConfigExt.FormConfigCtr getFormConfigCtr(String type, FormConfigExt formConfigExt) {
        FormConfigExt.FormConfigCtr formConfigCtr = null;
        switch (type) {
            case FORM:
                FormConfigExt.FormConfig formConfig = formConfigExt.getForm();
                if (formConfig == null) {
                    return null;
                }
                formConfigCtr = formConfig.getConfigCtr();
                break;
            case LIST:
                FormConfigExt.ListConfig listConfig = formConfigExt.getList();
                if (listConfig == null) {
                    return null;
                }
                formConfigCtr = listConfig.getConfigCtr();
                break;
            case QUERY:
                FormConfigExt.QueryConfig queryConfig = formConfigExt.getQuery();
                if (queryConfig == null) {
                    return null;
                }
                formConfigCtr = queryConfig.getConfigCtr();
        }
        return formConfigCtr;
    }

    public static Integer getController(FormConfigQueryVo formConfigQuery, String type) {
        return switch (type) {
            case FORM -> formConfigQuery.getFormController();
            case LIST -> formConfigQuery.getListController();
            case QUERY -> formConfigQuery.getQueryController();
            default -> null;
        };
    }

    public static String getI18nKey(String key) {
        if (key.startsWith("#i18n")) {
            return key.substring(6, key.length() - 1);
        }
        return null;
    }

    public static boolean isI18nKey(String key) {
        return key.startsWith("#i18n");
    }

    public static File getPageDir() throws Exception {
        return Paths.get(Objects.requireNonNull(FormUtil.class.getClassLoader().getResource(PAGE_ROOT)).toURI()).toFile();
    }

    public static File getTplDir() throws Exception {
        return Paths.get(Objects.requireNonNull(FormUtil.class.getClassLoader().getResource(TPL_PAGE_ROOT)).toURI()).toFile();
    }

    public static File getSysPageDir() {
        return new File(Objects.requireNonNull(FormUtil.class.getClassLoader().getResource(SYSTEM_PAGE_ROOT)).getFile());
    }

    public static File getAppPageDir() {
        return new File(Objects.requireNonNull(FormUtil.class.getClassLoader().getResource(APP_PAGE_ROOT)).getFile());
    }

    public static File getTmpPageDir() {
        return new File(Objects.requireNonNull(FormUtil.class.getClassLoader().getResource(TMP_PAGE_ROOT)).getFile());
    }

    public static boolean genForm(Long formId, String domain, String contextPath, Locale locale) throws Exception {
        FormRepo formRepo = SpringUtil.getBean(FormRepo.class);
        FormConfigRepo formConfigRepo = SpringUtil.getBean(FormConfigRepo.class);
        FormDefDetail formDefDetail = formConfigRepo.getFormDetail(formId);
        String content = null;
        try {
            content = formRepo.getFormRawContentById(formId);
        } catch (Exception ignored) {
        }
        Page page = new Page();
        if (content != null) {
            TmpFromStruct tmpFromStruct = JacksonUtil.json2Object(content, TmpFromStruct.class);
            if (tmpFromStruct == null) {
                throw new RuntimeException("form content is null");
            }
            page.setMeta(tmpFromStruct.getMeta());
        } else {
            FormProperties.Meta meta = new FormProperties.Meta();
            page.setMeta(meta);
        }

        FormDetailExt ext = getFormDetailExt(formDefDetail.getExt());
        if (ext == null) {
            throw new RuntimeException("未配置表单扩展信息");
        }
        Integer tpl = formDefDetail.getTpl();
        if (tpl == null) {
            throw new RuntimeException("未配置表单模板");
        }

        GenFormContextHolder.init(GenFormContextHolder.GenFormInfo.builder().formDef(formRepo.getFormDef(formId))
                .domain(domain).contextPath(contextPath).formId(formId).locale(locale).build());
        if (!FormLayout.isList(tpl)) {
            handleTplDetail(page, formDefDetail, ext, formConfigRepo, formRepo);
            buildMeta(page, FormProperties.MetaType.LIST, formId);
        } else {
            List<FormConfigType> configTypes = FormDetailExt.getConfigTabs(ext.getConfigTabs(), formId);
            boolean packageType = configTypes.contains(FormConfigType.FORM) && configTypes.contains(FormConfigType.LIST);
            if (packageType) {
                list(page, formId, formDefDetail, formConfigRepo, Maps.newHashMap());
                buildMeta(page, FormProperties.MetaType.LIST, formId);
            } else {
                configTypes.forEach(type -> {
                    switch (type) {
                        case FORM:
                            try {
                                List<FormConfigQueryVo> formConfigQueryList = formConfigRepo.listMergedFormConfig(FormConfigQuerySearchVo.builder().formId(formId).build());
                                Form form = Form.buildForm(formId, page.getMeta(), formDefDetail, getValidateFormItems(formConfigQueryList), formId.toString(), formId.toString(), buildFormParams(formDefDetail));
                                FormDefDto formDefDto = buildFormConfig(formDefDetail, form);
                                formDefDto.setId(formId);
                                formRepo.updateFormDef(formDefDto);
                                page.setBody(List.of(form));
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                            buildMeta(page, FormProperties.MetaType.FORM, formId);
                            break;
                        case LIST:
                            list(page, formId, formDefDetail, formConfigRepo, Maps.newHashMap());
                            buildMeta(page, FormProperties.MetaType.LIST, formId);
                            break;
                    }
                });
            }
        }
        FormDefDto formDefDto = new FormDefDto();
        formDefDto.setType(formDefDetail.getKeyId());
        formDefDto.setName(formDefDetail.getKeyId());
        formDefDto.setId(formId);
        formDefDto.setContent(JacksonUtil.object2json(page, true));
        GenFormContextHolder.clear();
        return formRepo.updateFormDef(formDefDto);
    }

    public static FormDefDto buildFormConfig(FormDefDetail formDefDetail, Form form) {
        FormDefDto formDefDto = new FormDefDto();
        formDefDto.setType(formDefDetail.getKeyId());
        formDefDto.setName(formDefDetail.getKeyId() + "-form");
        formDefDto.setContent(JacksonUtil.object2json(form, true));
        return formDefDto;
    }

    private static void buildMeta(Page page, FormProperties.MetaType type, Long formId) {
        page.getMeta().type(type).genBy(formId.toString()).createBy(LoginCache.get().getUsername()).appendTo(formId.toString());
    }

    public static Map<String, String> buildListParams(FormDefDetail formDefDetail) {
        Map<String, String> parameters = new HashMap<>();
        FormDetailExt formDetailExt = FormUtil.getFormDetailExt(formDefDetail.getExt());
        if (formDetailExt != null && formDetailExt.getListExt() != null && MapUtils.isNotEmpty(formDetailExt.getListExt().getParamMapping())) {
            formDetailExt.getListExt().getParamMapping().forEach((k, v) -> {
                if (StringUtils.isNotBlank(k) && StringUtils.isNotBlank(v)) {
                    parameters.put(k, v);
                }
            });
        }
        return parameters;
    }

    public static Map<String, Object> buildFormParams(FormDefDetail formDefDetail) {
        Map<String, Object> data = new HashMap<>();
        if (formDefDetail == null) {
            return data;
        }
        FormDetailExt formDetailExt = FormUtil.getFormDetailExt(formDefDetail.getExt());
        if (formDetailExt != null && formDetailExt.getFormExt() != null && formDetailExt.getFormExt().getParamMapping() != null) {
            data.putAll(formDetailExt.getFormExt().getParamMapping());
        }
        return data;
    }

    private static void handleTplDetail(Component page, FormDefDetail formDefDetail, FormDetailExt ext, FormConfigRepo formConfigRepo, FormRepo formRepo) {
//        FormDetailExt.TplConfigExt tplConfigExt = ext.getTplConfigExt();
//        if (tplConfigExt.getTplConfigs() == null || tplConfigExt.getTplConfigs().isEmpty()) {
//            throw new RuntimeException("表单明细配置为空");
//        }
//        Map<Integer, List<FormDetailExt.TplConfig>> detailTypeGroupBy = tplConfigExt.getTplConfigs().stream().collect(Collectors.groupingBy(FormDetailExt.TplConfig::getDetailType));
//
//        List<FormDetailExt.TplConfig> details = detailTypeGroupBy.get(FormDetailExt.TplConfig.DetailInfoType.Page.getValue());
//        if (CollectionUtils.isNotEmpty(details)) {
//            List<Component> components = details.stream().map(tplConfig -> {
//                String formName = tplConfig.getFfName();
//                FormDefDetail targetFormDefDetail = formConfigRepo.getFormDetail(formRepo.getFormIdByName(formName));
//                if (targetFormDefDetail == null) {
//                    throw new RuntimeException("表单明细配置错误, 表单不存在,【" + formName + "】");
//                }
//                return handleTargetForm(tplConfig.getTitle(), tplConfig.getFormConfigItem(), targetFormDefDetail, formConfigRepo);
//            }).toList();
//            Group.groupComponents(FormLayout.of(formDefDetail.getLayout()), page, components);
//        } else {
//            details = detailTypeGroupBy.get(FormDetailExt.TplConfig.DetailInfoType.Controller.getValue());
//            if (CollectionUtils.isEmpty(details)) {
//                throw new BusinessException("表单明细配置错误, 未配置表单明细");
//            }
//            List<FormConfigQueryVo> vos = new ArrayList<>(details.stream().map(tplConfig -> {
//                FormConfigQueryVo formConfigQueryVo = new FormConfigQueryVo();
//                formConfigQueryVo.setName(tplConfig.getName());
//                formConfigQueryVo.setDescription(tplConfig.getDesc());
//                formConfigQueryVo.setFormController(tplConfig.getController());
//                return formConfigQueryVo;
//            }).toList());
//            FormConfigQueryVo formConfigQueryVo = new FormConfigQueryVo();
//            formConfigQueryVo.setName(DefaultColumn.id.name());
//            formConfigQueryVo.setFormController(FormComponentType.FORM_COM_HIDDEN.getValue());
//            vos.add(formConfigQueryVo);
//            Form form = Form.buildForm(formDefDetail.getFormId(), formDefDetail, BooleanUtils.toBoolean(tplConfigExt.getInit()), vos, tplConfigExt.getAction());
//            page.setBody(List.of(form));
//        }
    }

    private static Component handleTargetForm(String title, Integer formConfigItem, FormDefDetail formDefDetail, FormConfigRepo formConfigRepo) {
        FormDetailExt ext = getFormDetailExt(formDefDetail.getExt());
        if (ext == null) {
            throw new RuntimeException("未配置表单扩展信息,formId=" + formDefDetail.getFormId());
        }
        List<FormConfigType> types = FormDetailExt.getConfigTabs(ext.getConfigTabs(), formDefDetail.getFormId());
        if (CollectionUtils.isEmpty(types)) {
            throw new RuntimeException("未配置表单扩展信息,formId=" + formDefDetail.getFormId());
        }
        FormConfigType type = FormConfigType.of(formConfigItem);
        if (!type.equals(FormConfigType.FORM) && !type.equals(FormConfigType.LIST)) {
            throw new RuntimeException("表单类型配置错误只支持列表和表单类型,formId=" + formDefDetail.getFormId());
        }

        Form form = new Form();
        form.setTitle(title);
        form.setWrapWithPanel(true);
        form.setSubmitText("");
        if (type == FormConfigType.FORM) {
            Boolean formStatic = null;
            FormDetailExt.FormExt formExt = ext.getFormExt();
            Map<String, String> paramMapping = null;
            if (formExt != null) {
                formStatic = formExt.getFormStatic();
                paramMapping = formExt.getParamMapping();
            }
            form.setFormStatic(formStatic != null && formStatic);
            String keyParam;
            StringBuilder params = new StringBuilder();
            params.append("valueIds=${id}").append("&formId=").append(formDefDetail.getFormId());
            if (MapUtils.isNotEmpty(paramMapping)) {
                //fixme keyParam 暂时只支持一个参数
                keyParam = getParamName(Lists.newArrayList(paramMapping.keySet()).getFirst());
                Map.Entry<String, String> entry = Lists.newArrayList(paramMapping.entrySet()).getFirst();
                String paramName = entry.getValue();
                params.append("&").append(paramName).append("=").append("${").append(keyParam).append("}");
            }
            form.setInitApi(Api.builder().url(GenFormContextHolder.getDomain() + GenFormContextHolder.getContextPath() + "/form/get?" + params).method(HTTP_GET).sendOn("this.id >0").build());
            List<FormConfigQueryVo> formConfigQueryList = formConfigRepo.listMergedFormConfig(FormConfigQuerySearchVo.builder().formId(formDefDetail.getFormId()).build());
            formConfigQueryList = getValidateFormItemsWithoutHidden(formConfigQueryList, formDefDetail);
            List<Component> components = Lists.newArrayList();
            if (BooleanUtils.isTrue(form.getFormStatic())) {
                components.addAll(buildCanWithoutController(formConfigQueryList, FORM));
            } else {
                components.addAll(buildControllers(formConfigQueryList, FORM));
            }
            Group.groupComponents(FormLayout.of(formDefDetail.getLayout()), form, components);
        } else {
            Map<String, Object> parameters = Maps.newHashMap();
            FormDetailExt.ListExt listExt = ext.getListExt();
            if (listExt != null) {
                if (MapUtils.isNotEmpty(listExt.getParamMapping())) {
                    //fixme keyParam 暂时只支持一个参数
                    Map.Entry<String, String> entry = Lists.newArrayList(listExt.getParamMapping().entrySet()).getFirst();
                    String paramName = entry.getValue();
                    parameters.put(paramName, entry.getKey());
                }
            }
            list(form, formDefDetail.getFormId(), formDefDetail, formConfigRepo, parameters);
        }

        return form;
    }

    public static Long getLoadFormId(String url) {
        if (url.contains("/form/load/")) {
            return Long.parseLong(url.substring(url.lastIndexOf("/") + 1));
        }
        return null;
    }

    private static void list(Component component, Long formId, FormDefDetail formDefDetail, FormConfigRepo formConfigRepo, Map<String, Object> parameters) {
        boolean pageable = Objects.equals(formDefDetail.getPageable(), (byte) 1) || Objects.equals(formDefDetail.getPageable(), (byte) 2);
        List<FormConfigQueryVo> formConfigQueryList = formConfigRepo.listMergedFormConfig(FormConfigQuerySearchVo.builder().formId(formId).build());
        if (pageable) {
            if (CollectionUtils.isNotEmpty(formConfigQueryList)) {
                Crud crud = buildCrud(formId, formDefDetail, formConfigQueryList, parameters);
                component.setBody(List.of(crud));
            }
        } else {
            String domain = GenFormContextHolder.getContextPath();
            String contextPath = GenFormContextHolder.getContextPath();
            ServiceBody service = new ServiceBody(domain, contextPath);
            service.setId(getComponentId());
            service.setName(service.getId());
            Map<String, Object> data = Maps.newHashMap();
            data.put(FORM_ID, formDefDetail.getFormId());
            data.put("valueId", "${id}");
            data.putAll(FormUtil.buildListParams(formDefDetail));
            component.setBody(List.of(service));
            Flex flex = new Flex();
            flex.setJustify("flex-start");
            Style style = new Style();
            style.put("margin-bottom", 5);
            flex.setStyle(style);
            List<Component> items = Lists.newArrayList();
            Table2 table2 = Table2.build(formDefDetail, formConfigQueryList, service.getName());
            items.addAll(buildListExtComponents(formDefDetail, getValidateFormItems(formConfigQueryList), false, Set.of(OPERATION_HEADER)));
            flex.setItems(items);
            SearchBox searchBox = (SearchBox) items.stream().filter(com -> com instanceof SearchBox).findFirst().orElse(null);
            if (searchBox != null) {
                data.put(searchBox.getName(), "${" + searchBox.getName() + "}");
            }
            if (MapUtils.isNotEmpty(data)) {
                data.putAll(parameters);
            }
            service.setApi(Api.builder().method(HTTP_GET).url(domain + contextPath + "/form/list").data(data).build());
            service.setBody(List.of(flex, table2));
        }
    }

    public static String getParamName(String param) {
        if (StringUtils.isBlank(param)) {
            return "";
        }
        if (isVariable(param)) {
            return param.substring(2, param.length() - 1);
        }
        return param;
    }

    public static boolean isVariable(String value) {
        return StringUtils.isNotBlank(value) && value.startsWith("${") && value.endsWith("}");
    }

    public static String getStrPostValue(Map<String, Object> data, String name) {
        if (data.get(name) == null || StringUtils.isBlank(data.get(name).toString())) {
            return null;
        }
        return data.get(name).toString();
    }

    public static List<String> getListStrPostValue(Map<String, Object> data, String name) {
        Object value = data.get(name);
        if (value == null) {
            return Lists.newArrayList();
        }
        return Lists.newArrayList(value.toString().split(","));
    }

    public static List<Long> getListLongByStrValue(Map<String, Object> data, String name) {
        String value = getStrPostValue(data, name);
        if (StringUtils.isBlank(value)) {
            return Lists.newArrayList();
        }
        return Stream.of(value.split(",")).map(Long::parseLong).collect(Collectors.toList());
    }

    public static Boolean getBooleanValue(Map<String, Object> data, String name) {
        if (data.get(name) == null || StringUtils.isBlank(data.get(name).toString())) {
            return null;
        }
        return BooleanUtils.toBoolean(data.get(name).toString());
    }

    public static Long getLongPostValue(Map<String, Object> data, String name) {
        if (data.get(name) == null || StringUtils.isBlank(data.get(name).toString())) {
            return null;
        }
        return Long.parseLong(data.get(name).toString());
    }

    public static Integer getIntPostValue(Map<String, Object> data, String name) {
        if (data.get(name) == null || StringUtils.isBlank(data.get(name).toString())) {
            return null;
        }
        return Integer.valueOf(data.get(name).toString());
    }

    public static void replaceUrl(JsonNode rootNode, String field, String newDomain, String formName) {
        List<JsonNode> apiNodes = rootNode.findParents(field);
        apiNodes.forEach(parent -> {
            if (parent.isObject()) {
                FormUrlInfo formUrlInfo = getUrl(parent, field, formName);
                if (formUrlInfo == null) {
                    return;
                }
                String newUrl = newUrl(formUrlInfo, newDomain);
                JsonNode node = parent.get(field);
                if (node.isObject()) {
                    ((ObjectNode) node).put("url", newUrl);
                } else if (node.isTextual()) {
                    ((ObjectNode) parent).set(field, TextNode.valueOf(newUrl));
                }
            }
        });
    }

    public static FormUrlInfo getUrl(String url, String formName) {
        FormUrlInfo formUrlInfo = new FormUrlInfo();
        if (url == null || (!url.contains("http") && !url.contains("https"))) {
            log.info("无法处理url:{}", url);
            return null;
        }
        String prefix = null;
        //处理get:http://www.xxx.com:8080/xxx/xxx?xxx=xxx
        if (!url.startsWith("http") && !url.startsWith("https") && !url.startsWith("${")) {
            int index = url.indexOf(":");
            if (index > 0) {
                prefix = url.substring(0, index);
                url = url.substring(index + 1);
            }
        }
        int indexOfQuery = url.indexOf("?");
        if (indexOfQuery > 0) {
            formUrlInfo.setQuery(url.substring(indexOfQuery + 1));
            for (String kv : formUrlInfo.getQuery().split("&")) {
                String[] params = kv.split("=");
                if (params.length == 2) {
                    formUrlInfo.getParams().put(params[0], params[1]);
                }
            }
            url = url.substring(0, indexOfQuery);
        }
        try {
            if (url.contains("{") && url.contains("}") && url.endsWith("}")) {
                url = url.substring(0, url.indexOf("${"));
            }
            formUrlInfo.setUrl(URI.create(url).toURL());
        } catch (Exception e) {
            log.error("url格式错误, url = {}", url);
            throw new BusinessException("url格式错误, url = " + url + ", formName = " + formName);
        }
        formUrlInfo.setPrefix(prefix);
        return formUrlInfo;
    }

    private static FormUrlInfo getUrl(JsonNode parent, String field, String formName) {
        String url = null;
        boolean object = false;
        JsonNode node = parent.get(field);
        if (node.isObject()) {
            url = node.get("url").asText();
            object = true;
        } else if (node.isTextual()) {
            url = node.asText();
        }
        FormUrlInfo formUrlInfo = getUrl(url, formName);
        if (formUrlInfo != null) {
            formUrlInfo.setObject(object);
        }
        return formUrlInfo;
    }

    private static String newUrl(FormUrlInfo formUrlInfo, String newDomain) {
        String ret;
        URL url = formUrlInfo.getUrl();
        if (!newDomain.startsWith("http")) {
            ret = url.getProtocol() + "://" + newDomain + url.getPath() + "?" + formUrlInfo.getQuery();
        } else {
            ret = newDomain + url.getPath() + "?" + formUrlInfo.getQuery();
        }
        if (StringUtils.isNotBlank(formUrlInfo.prefix)) {
            return formUrlInfo.prefix + ":" + ret;
        }
        return ret;
    }

    public static String getFormAuthCodePrefix(FormDef formDef, String code) {
        return getFormAuthCodePrefix(formDef, code, null);
    }

    public static String getFormAuthCodePrefix(FormDef formDef, String code, String type) {
        if (StringUtils.isBlank(code)) {
            return formDef.getType() + ":" + formDef.getFormId() + (StringUtils.isEmpty(type) ? "" : ":" + type);
        }
        return code;
    }

    public static String getFormAuthCode(FormDef formDef, FormElementInfo item) {
        String type = null;
        String suffix = null;
        if (item != null) {
            String authId = item.getAuthId();
            type = AuthElementTypeEnum.shortName(item.getName());
            if (CollectionUtils.isNotEmpty(item.getUrls())) {
                String key = item.getUrls().stream().map(u -> u.getUrl().getPath()).collect(Collectors.joining(",")) + (StringUtils.isNotEmpty(authId) ? authId : "");
                String md5 = MD5Util.crypto2(key);
                suffix = md5.substring(md5.length() - 5);
            }
        }
        return formDef.getType() + ":" + formDef.getFormId() + (StringUtils.isEmpty(type) ? "" : ":" + type)
                + ":" + (suffix == null ? ThreadLocalRandom.current().nextInt(100, 9999) : suffix);
    }

    public static boolean isMenuCodeInValid(String code) {
        // 正则表达式解释：
        // ^       - 字符串开始
        // .+      - 一个或多个任意字符（第一部分xxx）
        // :       - 冒号分隔符
        // \\d+    - 一个或多个数字
        // :       - 冒号分隔符
        // .+      - 一个或多个任意字符（第二部分xxx）
        // $       - 字符串结束
        String regex = "^.+:\\d+:.+$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(code);
        return !matcher.matches();
    }

    public static String getFormIdByMenuCode(String code) {
        if (StringUtils.isBlank(code)) {
            return null;
        }
        if (isMenuCodeInValid(code)) {
            return null;
        }
        return code.split(":")[1];
    }

    public static Action getMethodAction(Class<?> formHandle, String actionName) {
        for (Method method : formHandle.getMethods()) {
            Action annotation = AnnotationUtils.findAnnotation(method, Action.class);
            if (annotation == null) {
                continue;
            }
            String name = annotation.value();
            if (StringUtils.isBlank(annotation.value())) {
                name = method.getName();
            }
            if (Objects.equals(name, actionName)) {
                return annotation;
            }
        }
        return null;
    }

    public static List<Select.Option> getHandleActionOptions(Class<?> formHandle) {
        return Streams.of(formHandle.getMethods()).map(method -> {
            Action annotation = AnnotationUtils.findAnnotation(method, Action.class);
            if (annotation == null) {
                return null;
            }
            String name = annotation.value();
            if (StringUtils.isBlank(annotation.value())) {
                name = method.getName();
            }
            Select.Option operation = new Select.Option();
            Log log = AnnotationUtils.findAnnotation(method, Log.class);
            String label = name;
            if (log != null) {
                label = name + "(" + log.value() + ")";
            }
            operation.setLabel(label);
            operation.setValue(name);
            return operation;
        }).filter(Objects::nonNull).toList();
    }

    public static String getFormActionComment(Class<?> clazz, String actionName) {
        for (Method method : clazz.getDeclaredMethods()) {
            Action action = AnnotationUtils.findAnnotation(method, Action.class);
            if (action == null) {
                continue;
            }
            String value = action.value();
            if (StringUtils.isBlank(value)) {
                value = method.getName();
            }
            if (Objects.equals(value, actionName)) {
                String comment = action.comment();
                if (StringUtils.isBlank(comment)) {
                    Log log = AnnotationUtils.findAnnotation(method, Log.class);
                    if (log != null) {
                        comment = log.value();
                    }
                }
                return comment;
            }
        }
        return null;
    }

    public static String getControllerComment(Class<?> clazz, String url) {
        RequestMapping classMapping = AnnotationUtils.findAnnotation(clazz, RequestMapping.class);
        String classPath = "";
        if (classMapping != null && classMapping.value().length > 0) {
            classPath = classMapping.value()[0];
        }

        // 遍历所有方法
        for (Method method : clazz.getDeclaredMethods()) {
            // 检查方法上的各种映射注解
            String methodPath = getMethodMappingPath(method);
            if (methodPath != null) {
                String fullPath = combinePaths(classPath, methodPath);
                if (Objects.equals(fullPath, url)) {
                    io.swagger.v3.oas.annotations.Operation operation = AnnotationUtils.findAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);
                    if (operation == null) {
                        throw new BusinessException("控制器定义缺少操作注释，bean = " + clazz.getName());
                    }
                    return operation.summary();
                }
            }
        }
        return null;
    }

    public static FormProperties.Meta getMeta(String content, String formName) {
        JsonNode root = readJson(content);
        if (root == null) {
            throw new BusinessException("表单定义格式错误");
        }
        return getMeta(root, formName);
    }

    public static FormProperties.Meta getMeta(JsonNode root, String formName) {
        if (root == null) {
            throw new BusinessException("表单定义格式错误");
        }
        List<JsonNode> metaNodes = root.findValues(FormProperties.FORM_META);
        if (CollectionUtils.isEmpty(metaNodes)) {
            throw new BusinessException("表单定义缺少元数据,表单 = " + formName);
        }
        JsonNode metaNode = metaNodes.getFirst();
        if (!metaNode.has("title")) {
            throw new BusinessException("表单元数据缺少title属性,表单 = " + formName);
        }
        FormProperties.Meta meta = new FormProperties.Meta();
        meta.title(metaNode.get("title").asText());
        if (metaNode.has("genBy")) {
            meta.genBy(metaNode.get("genBy").asText());
        }
        if (metaNode.has("type")) {
            meta.type(FormProperties.MetaType.of(metaNode.get("type").asText()));
        }
        if (metaNode.has("appendTo")) {
            meta.appendTo(metaNode.get("appendTo").asText());
        }
        if (metaNode.has("createBy")) {
            meta.createBy(metaNode.get("createBy").asText());
        }
        return meta;
    }

    public static <T> T dataSourceInvoke(String tagetDs, DsInvoke<T> action) {
        String dsKey = null;
        try {
            dsKey = DataSourceContextHolder.getDataSourceKey();
            DataSourceContextHolder.setDataSourceKey(tagetDs);
            return action.invoke();
        } finally {
            DataSourceContextHolder.setDataSourceKey(dsKey);
        }
    }

    public static Action getMethodAction(ApiDef apiDef) {
        String[] handleAndAction = apiDef.getExt().split(",");
        if (handleAndAction.length == 2) {
            FormDataHandle formDataHandle = SpringUtil.getBean(handleAndAction[0], FormDataHandle.class);
            if (formDataHandle != null) {
                return FormUtil.getMethodAction(formDataHandle.getClass(), handleAndAction[1]);
            }
        }
        return null;
    }

    public static List<Parameter> getActionParameters(ApiDef apiDef) {
        Action action = getMethodAction(apiDef);
        if (action != null) {
            return List.of(action.outputs());
        }
        return List.of();
    }

    public static String getSearchValue(Map<String, String[]> data, String name) {
        String[] value = data.get(name);
        if (ArrayUtils.isNotEmpty(value)) {
            return value[0];
        }
        return null;
    }

    public static ApiDef getApiDef(String code) {
        return FormUtil.dataSourceInvoke(DataSourceContextHolder.getDefaultDs(), () -> {
            ApiDef apiDef = SpringUtil.getBean(ApiRepo.class).getApiDef(code);
            if (apiDef == null) {
                throw new BusinessException("接口不存在,code = " + code);
            }
            return apiDef;
        });
    }

    public static DataModel getDataModel(String modelName) {
        return FormUtil.dataSourceInvoke(DataSourceContextHolder.getDefaultDs(), () -> {
            DataModel dataModel = SpringUtil.getBean(DataModelRepo.class).getDataModel(modelName);
            if (dataModel == null) {
                throw new BusinessException("数据模型不存在,modelName = " + modelName);
            }
            return dataModel;
        });
    }

    public static List<DataModelDetail> getDataModelDetails(String modelName) {
        return FormUtil.dataSourceInvoke(DataSourceContextHolder.getDefaultDs(), () -> SpringUtil.getBean(DataModelRepo.class).getDetails(DataModelDetailQuery.builder().modelName(modelName).build()));
    }

    public static Map<String, Collection<Object>> convertToCollectionMap(Map<String, Object> item) {
        Map<String, Collection<Object>> ret = Maps.newLinkedHashMap();
        item.forEach((k, v) -> ret.compute(k, (k1, v1) -> {
            if (v1 == null) {
                v1 = Lists.newArrayList();
            }
            if (v instanceof Collection<?> c) {
                v1.addAll(c);
            } else {
                if (v instanceof String cv && cv.contains(",")) {
                    v1.addAll(Arrays.asList(cv.split(",")));
                } else {
                    v1.add(v);
                }
            }
            return v1;
        }));
        return ret;
    }

    public static StringBuilder getChainApi(String domain, String contextPath, String chain) {
        StringBuilder api = new StringBuilder();
        api.append(domain).append(contextPath).append("/form/app/");
        if (StringUtils.isBlank(chain)) {
            throw new RuntimeException("目标动作类型操作配置错误");
        }
        String[] handleAndAction = chain.split(",");
        if (handleAndAction.length != 2) {
            throw new RuntimeException("目标动作类型操作配置错误");
        }
        api.append(handleAndAction[0]).append("/").append(handleAndAction[1]);
        return api;
    }

    public static List<String> extractSqlVariables(String sql) {
        List<String> variables = new ArrayList<>();
        Pattern pattern = Pattern.compile("@([a-zA-Z0-9_]+)");
        Matcher matcher = pattern.matcher(sql);
        while (matcher.find()) {
            variables.add(matcher.group().replace("@", ""));
        }
        return variables;
    }

    private static List<String> initNoneAuthUrl(JsonNode root) {
        List<String> urls = new ArrayList<>();
        List<JsonNode> authParentNodes = root.findParents(AUTH_KEY);
        if (CollectionUtils.isEmpty(authParentNodes)) {
            return urls;
        }
//        for (JsonNode authParentNode : authParentNodes) {
//            if (authParentNode instanceof ObjectNode) {
//                TextNode textNode = (TextNode) authParentNode.get(AUTH_KEY);
//                if (Objects.equals(textNode.asText(), AUTH_NONE)) {
//                    REMOTE_API_KEYS.forEach(conf -> authParentNode.findParents(conf.getKey()).forEach(parent -> {
//                        FormUrlInfo formUrlInfo = getUrl(parent, conf.getKey(), formName);
//                        if (formUrlInfo != null && formUrlInfo.url != null) {
//                            urls.add(formUrlInfo.url.getPath());
//                        }
//                    }));
//                }
//            }
//        }
        return urls;
    }

    public static List<FormElementInfo> getFormElementInfos(JsonNode rootNode, FormProperties formProperties, AuthElement key) {
        List<FormElementInfo> nodes = new ArrayList<>();
        JsonFinder.findNodesWithParentCheck(rootNode, key.getName(), key.getStopNames(), nodeInfo -> {
            JsonNode handleNode = nodeInfo.node();
            JsonNode parentNode = nodeInfo.parentNode();
            if (parentNode != null) {
                if (parentNode instanceof ArrayNode && nodeInfo.parentChain().size() > 1) {
                    parentNode = nodeInfo.parentChain().get(nodeInfo.parentChain().size() - 2);
                }
                JsonNode typeNode = parentNode.get(TYPE_KEY);
                if (typeNode != null && Objects.equals(typeNode.asText(), EACH_KEY)) {
                    handleNode = parentNode;
                }
            }

            FormElementInfo formElementInfo = new FormElementInfo();
            formElementInfo.setName(key.getName());
            Set<FormUrlInfo> urls = Sets.newHashSet();
            formElementInfo.setUrls(urls);
            formElementInfo.setNode(handleNode);
            JsonNode authNode = handleNode.get(AUTH_KEY);
            if (authNode != null) {
                formElementInfo.setCode(authNode.asText());
            }
            JsonNode authIdNode = handleNode.get(AUTH_ID_KEY);
            if (authIdNode != null) {
                formElementInfo.setAuthId(authIdNode.asText());
            }
            formElementInfo.setLabel(getLabel(formProperties, parentNode, handleNode));
            nodes.add(formElementInfo);
            for (UrlFindInfo urlFindInfo : key.getUrlNames()) {
                JsonNode pathNode = null;
                boolean isPath = false;
                String targetName = urlFindInfo.getName();
                String name = urlFindInfo.getName();
                String[] path = name.split("/");
                if (path.length > 1) {
                    isPath = true;
                    pathNode = handleNode;
                    for (int i = 0; i < path.length; i++) {
                        if (pathNode == null) {
                            break;
                        }
                        if (i + 1 != path.length) {
                            pathNode = pathNode.get(path[i]);
                        } else {
                            name = path[i];
                        }
                    }
                    targetName = name;
                }
                if (!key.isAccessInner() && !isPath) {
                    JsonNode urlNode = handleNode.get(name);
                    if (urlNode != null) {
                        if (urlNode instanceof TextNode) {
                            FormUrlInfo formUrlInfo = getUrl(urlNode.asText(), formProperties.getName());
                            if (formUrlInfo != null) {
                                urls.add(formUrlInfo);
                            }
                        } else {
                            if (CollectionUtils.isNotEmpty(key.getFields())) {
                                key.getFields().forEach(field -> {
                                    FormUrlInfo formUrlInfo = getUrl(urlNode.get(field).asText(), formProperties.getName());
                                    if (formUrlInfo != null) {
                                        urls.add(formUrlInfo);
                                    }
                                });
                            }
                        }
                    }
                } else {
                    List<String> results = Lists.newArrayList();
                    if (CollectionUtils.isNotEmpty(key.getFields())) {
                        for (String field : key.getFields()) {
                            traverse(pathNode == null ? handleNode : pathNode, targetName, field, key.getInnerStopNames(), results);
                        }
                    } else {
                        traverse(pathNode == null ? handleNode : pathNode, targetName, null, key.getInnerStopNames(), results);
                    }
                    results.forEach(ret -> {
                        FormUrlInfo formUrlInfo = getUrl(ret, formProperties.getName());
                        if (formUrlInfo != null) {
                            urls.add(formUrlInfo);
                        }
                    });
                }
            }
        });
        return nodes;
    }

    private static String getLabel(FormProperties formProperties, JsonNode parentNode, JsonNode handleNode) {
        JsonNode labelNode = handleNode.get(LABEL_KEY);
        if (labelNode != null) {
            return FormI18nHandle.getValue(labelNode.asText());
        } else {
            JsonNode titleNode = handleNode.get(TITLE_KEY);
            if (titleNode != null) {
                return FormI18nHandle.getValue(titleNode.asText());
            }
        }
        if (parentNode == null) {
            if (formProperties.getMeta().getTitle() != null) {
                return FormI18nHandle.getValue(formProperties.getMeta().getTitle());
            }
        } else {
            JsonNode typeNode = parentNode.get(TYPE_KEY);
            String typeValue = null;
            if (typeNode != null) {
                typeValue = typeNode.asText();
            }
            if (Objects.equals(typeValue, PAGE_KEY) || Objects.equals(typeValue, CONTAINER_KEY) || Objects.equals(typeValue, WRAPPER_KEY)) {
                return FormI18nHandle.getValue(formProperties.getMeta().getTitle());
            }
            labelNode = parentNode.get(LABEL_KEY);
            if (labelNode != null) {
                return FormI18nHandle.getValue(labelNode.asText());
            } else {
                JsonNode titleNode = parentNode.get(TITLE_KEY);
                if (titleNode != null) {
                    return FormI18nHandle.getValue(titleNode.asText());
                }
            }
        }
        return null;
    }

    private static String getMethodMappingPath(Method method) {
        // 检查所有可能的映射注解
        GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
        if (getMapping != null && getMapping.value().length > 0) {
            return getMapping.value()[0];
        }

        PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
        if (postMapping != null && postMapping.value().length > 0) {
            return postMapping.value()[0];
        }

        PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
        if (putMapping != null && putMapping.value().length > 0) {
            return putMapping.value()[0];
        }

        DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
        if (deleteMapping != null && deleteMapping.value().length > 0) {
            return deleteMapping.value()[0];
        }

        RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
        if (requestMapping != null && requestMapping.value().length > 0) {
            return requestMapping.value()[0];
        }

        return null;
    }

    private static String combinePaths(String classPath, String methodPath) {
        if (classPath.isEmpty()) {
            return methodPath;
        }
        if (methodPath.isEmpty()) {
            return classPath;
        }

        // 处理路径拼接，确保中间有一个斜杠
        if (classPath.endsWith("/") && methodPath.startsWith("/")) {
            return classPath + methodPath.substring(1);
        } else if (!classPath.endsWith("/") && !methodPath.startsWith("/")) {
            return classPath + "/" + methodPath;
        } else {
            return classPath + methodPath;
        }
    }

    public static Locale getLocale(Map<String, Object> formData) {
        String localeStr = getStrPostValue(formData, FormConstants.LOCALE_PARAM);
        Locale locale = Locale.getDefault();
        if (StringUtils.isNotBlank(localeStr)) {
            locale = Locale.forLanguageTag(localeStr);
        }
        return locale;
    }

    @FunctionalInterface
    public interface DsInvoke<T> {
        T invoke();
    }

    @Data
    public static class FormUrlInfo {
        private URL url;
        private Map<String, String> params = Maps.newHashMap();
        private String query;
        private String prefix;
        private boolean object;
    }

    @Data
    @ToString(of = {"name", "urls", "code"})
    public static class FormElementInfo {
        private String name;
        private Set<FormUrlInfo> urls;
        private Map<Long, Set<FormUrlInfo>> addUrls = Maps.newHashMap();
        private String code;
        private String authId;
        private JsonNode node;
        private String label;
    }

    @Builder
    public record FormElementAddUrl(Long fromFormId, Set<String> urls) {
    }
}
